import threading
import time
from pynput import keyboard
from PyQt5.QtCore import QObject, pyqtSignal, QMutex, QTimer, pyqtSlot

class HotkeyListener(QObject):
    """全局热键监听器"""
    hotkey_triggered = pyqtSignal()
    
    def __init__(self, hotkey_str="Ctrl+Space"):
        super().__init__()
        self.hotkey_str = hotkey_str
        self.listener = None
        self.thread = None
        self.running = False
        self.current_keys = set()
        self.last_trigger_time = 0  # 防止重复触发
        self.cooldown = 0.5  # 冷却时间（秒）
        self.mutex = QMutex()  # 线程安全的锁
        
        # 解析热键
        self.parse_hotkey(hotkey_str)
        
    def parse_hotkey(self, hotkey_str):
        """解析热键字符串"""
        self.hotkey = set()
        self.key_names = set()  # 记录键名，用于后续匹配
        parts = hotkey_str.split("+")
        
        # 映射修饰键，包含左右键的区别
        key_map = {
            "Ctrl": [keyboard.Key.ctrl, keyboard.Key.ctrl_l, keyboard.Key.ctrl_r],
            "Alt": [keyboard.Key.alt, keyboard.Key.alt_l, keyboard.Key.alt_r],
            "Shift": [keyboard.Key.shift, keyboard.Key.shift_l, keyboard.Key.shift_r],
            "Space": [keyboard.Key.space],
            "Tab": [keyboard.Key.tab],
            "Esc": [keyboard.Key.esc],
        }
        
        # 为每个部分添加对应键
        for part in parts:
            part = part.strip()
            self.key_names.add(part)  # 添加到键名集合
            
            if part in key_map:
                # 将主键和左右变体都添加到匹配集
                self.hotkey.update(key_map[part])
            elif len(part) == 1:
                # 字母和数字按键
                self.hotkey.add(keyboard.KeyCode.from_char(part.lower()))
            elif part.startswith("F") and len(part) <= 3:
                try:
                    f_num = int(part[1:])
                    if 1 <= f_num <= 12:
                        key_attr = f"f{f_num}"
                        if hasattr(keyboard.Key, key_attr):
                            self.hotkey.add(getattr(keyboard.Key, key_attr))
                except:
                    pass
        
        print(f"已设置快捷键: {hotkey_str}, 重点键名: {self.key_names}, 匹配键集: {self.hotkey}")
        
    def matches_hotkey(self, pressed_keys):
        """检查按下的键是否匹配我们的热键组合"""
        # 特殊处理：检查修饰键
        has_ctrl = any(key in pressed_keys for key in [keyboard.Key.ctrl, keyboard.Key.ctrl_l, keyboard.Key.ctrl_r])
        has_alt = any(key in pressed_keys for key in [keyboard.Key.alt, keyboard.Key.alt_l, keyboard.Key.alt_r])
        has_shift = any(key in pressed_keys for key in [keyboard.Key.shift, keyboard.Key.shift_l, keyboard.Key.shift_r])
        has_space = keyboard.Key.space in pressed_keys
        
        # 修饰键检查
        required_modifiers = self.key_names.intersection({"Ctrl", "Alt", "Shift"})
        if "Ctrl" in required_modifiers and not has_ctrl:
            return False
        if "Alt" in required_modifiers and not has_alt:
            return False
        if "Shift" in required_modifiers and not has_shift:
            return False
            
        # 空格键检查
        if "Space" in self.key_names and not has_space:
            return False
            
        # 检查完整的组合 - 确保所有必要的键都被按下
        required_keys_count = len(self.key_names)
        detected_keys_count = sum([
            1 if has_ctrl and "Ctrl" in self.key_names else 0,
            1 if has_alt and "Alt" in self.key_names else 0,
            1 if has_shift and "Shift" in self.key_names else 0,
            1 if has_space and "Space" in self.key_names else 0
        ])
        
        # 如果只有修饰键和空格键，并且全部匹配，返回成功
        if required_keys_count == detected_keys_count and required_keys_count > 0:
            return True
        
        # 检查其他按键
        for key_name in self.key_names:
            if key_name not in ["Ctrl", "Alt", "Shift", "Space"]:
                # 处理单字符键
                if len(key_name) == 1:
                    char_detected = False
                    for key in pressed_keys:
                        if isinstance(key, keyboard.KeyCode) and hasattr(key, 'char') and key.char and key.char.lower() == key_name.lower():
                            char_detected = True
                            break
                    if not char_detected:
                        return False
                # 处理功能键
                elif key_name.startswith("F") and len(key_name) <= 3:
                    try:
                        f_num = int(key_name[1:])
                        if 1 <= f_num <= 12:
                            f_key = getattr(keyboard.Key, f"f{f_num}")
                            if f_key not in pressed_keys:
                                return False
                    except:
                        pass
        
        return True
        
    @pyqtSlot()
    def emit_signal(self):
        """在主线程中发射热键触发信号"""
        print("发送热键触发信号到主线程...")
        self.hotkey_triggered.emit()
        print("信号已发送")

    def on_press(self, key):
        """按键按下事件"""
        try:
            self.mutex.lock()
            
            # 记录按下的键
            self.current_keys.add(key)
            
            # 使用改进的匹配方法
            current_time = time.time()
            if (len(self.current_keys) >= len(self.key_names) and  # 确保至少有足够的键
                self.matches_hotkey(self.current_keys) and
                current_time - self.last_trigger_time > self.cooldown):
                
                self.last_trigger_time = current_time
                print(f"热键触发: {self.hotkey_str}, 当前按下的键: {self.current_keys}")
                
                # 使用 QMetaObject.invokeMethod 在主线程中发射信号
                from PyQt5.QtCore import QMetaObject, Qt
                QMetaObject.invokeMethod(self, "emit_signal", Qt.QueuedConnection)
        except Exception as e:
            print(f"处理按键时出错: {e}")
        finally:
            self.mutex.unlock()
    
    def on_release(self, key):
        """按键释放事件"""
        try:
            self.mutex.lock()
            if key in self.current_keys:
                self.current_keys.remove(key)
                # 减少输出量，只在调试时使用
                # print(f"释放键: {key}, 剩余键: {self.current_keys}")
        finally:
            self.mutex.unlock()
        
    def start(self):
        """开始监听"""
        if self.running:
            return
            
        print(f"开始监听快捷键: {self.hotkey_str}")
        
        def listen_thread():
            try:
                with keyboard.Listener(on_press=self.on_press, on_release=self.on_release) as listener:
                    self.listener = listener
                    listener.join()
            except Exception as e:
                print(f"快捷键监听线程出错: {e}")
        
        self.thread = threading.Thread(target=listen_thread, daemon=True)
        self.thread.start()
        self.running = True
    
    def stop(self):
        """停止监听"""
        if not self.running:
            return
        
        if self.listener:
            self.listener.stop()
            self.listener = None
        
        self.running = False
        print("已停止快捷键监听")
    
    def update_hotkey(self, hotkey_str):
        """更新热键"""
        if self.hotkey_str != hotkey_str:
            print(f"更新快捷键: {self.hotkey_str} -> {hotkey_str}")
            self.hotkey_str = hotkey_str
            self.parse_hotkey(hotkey_str)
            
            # 重启监听
            if self.running:
                self.stop()
                self.start()

# 如果需要测试模块
if __name__ == "__main__":
    import sys
    from PyQt5.QtWidgets import QApplication, QLabel, QMainWindow, QVBoxLayout, QWidget, QPushButton
    
    app = QApplication(sys.argv)
    
    window = QMainWindow()
    window.setWindowTitle("热键测试")
    window.resize(300, 200)
    
    central = QWidget()
    layout = QVBoxLayout(central)
    
    label = QLabel("按下 Ctrl+Space 测试热键")
    layout.addWidget(label)
    
    test_button = QPushButton("测试修改快捷键")
    layout.addWidget(test_button)
    
    window.setCentralWidget(central)
    window.show()
    
    # 创建热键监听器
    listener = HotkeyListener("Ctrl+Space")
    listener.hotkey_triggered.connect(lambda: label.setText(f"热键已触发! 时间: {time.time()}"))
    listener.start()
    
    # 测试更新快捷键
    test_button.clicked.connect(lambda: listener.update_hotkey("Alt+X"))
    
    sys.exit(app.exec_())
